<?php if(! defined('SYSPATH')) exit('No direct script access allowed');
/*
 +----------------------------------------------------------------------+
 | QuickPHP Framework Version 0.10                                      |
 +----------------------------------------------------------------------+
 | Copyright (c) 2010 QuickPHP.net All rights reserved.                 |
 +----------------------------------------------------------------------+
 | Licensed under the Apache License, Version 2.0 (the 'License');      |
 | you may not use this file except in compliance with the License.     |
 | You may obtain a copy of the License at                              |
 | http://www.apache.org/licenses/LICENSE-2.0                           |
 | Unless required by applicable law or agreed to in writing, software  |
 | distributed under the License is distributed on an 'AS IS' BASIS,    |
 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or      |
 | implied. See the License for the specific language governing         |
 | permissions and limitations under the License.                       |
 +----------------------------------------------------------------------+
 | Author: BoPo <ibopo@126.com>                                         |
 +----------------------------------------------------------------------+
*/
/**
 * MySQL database connection.
 *
 * @category   QuickPHP
 * @package    Database
 * @author     QuickPHP Team
 * @copyright  (c) 2008-2009 QuickPHP Team
 * @license    http://www.QuickPHP.net/license
 */
class QuickPHP_Database_Driver_Mysql extends QuickPHP_Database_Abstract
{

    // Database in use by each connection
    protected static $_current_databases = array();

    // Use SET NAMES to set the character set
    protected static $_set_names;

    // Identifier for this connection within the PHP driver
    protected $_connection_id;

    // MySQL uses a backtick for identifiers
    protected $_identifier = '`';

    public function connect()
    {
        $persistent = $hostname = $username = $password = $database = FALSE;

        if($this->_connection) return;

        if(Database_Driver_Mysql::$_set_names === NULL)
        {
            // Determine if we can use mysqli_set_charset(), which is only
            // available on PHP 5.2.3+ when compiled against MySQL 5.0+
            Database_Driver_Mysql::$_set_names = ! function_exists('mysqli_set_charset');
        }

        // Extract the connection parameters, adding required variabels
        extract($this->_config['connection'] + array('database' => '', 'hostname' => '', 'port' => NULL, 'socket' => NULL, 'username' => '', 'password' => '', 'persistent' => FALSE));

        // Prevent this information from showing up in traces
        unset($this->_config['connection']['username'], $this->_config['connection']['password']);

        try
        {
            $this->_connection = mysqli_connect($hostname, $username, $password);
        }
        catch(ErrorException $e)
        {
            // No connection exists
            $this->_connection = NULL;
            throw $e;
        }

        // \xFF is a better delimiter, but the PHP driver uses underscore
        $this->_connection_id = sha1($hostname . '_' . $username . '_' . $password);
        $this->_select_db($database);

        if(! empty($this->_config['charset']))
        {
            // Set the character set
            $this->set_charset($this->_config['charset']);
        }
    }

    /**
     * Select the database
     *
     * @param   string  Database
     * @return  void
     */
    protected function _select_db($database)
    {
        if(! mysqli_select_db($database, $this->_connection))
        {
            // Unable to select database
            throw new Database_Exception(':error', array(':error' => mysqli_error($this->_connection)), mysqli_errno($this->_connection));
        }

        Database_Driver_Mysql::$_current_databases[$this->_connection_id] = $database;
    }

    public function disconnect()
    {
        try
        {
            // Database is assumed disconnected
            $status = TRUE;

            if(is_resource($this->_connection))
            {
                $status = mysqli_close($this->_connection);

                if(! empty($status))
                {
                    // Clear the connection
                    $this->_connection = NULL;
                }
            }
        }
        catch(Exception $e)
        {
            // Database is probably not disconnected
            $status = ! is_resource($this->_connection);
        }

        return $status;
    }

    public function set_charset($charset)
    {
        // Make sure the database is connected
        $this->_connection or $this->connect();

        if(Database_Driver_Mysql::$_set_names === TRUE)
        {
            // PHP is compiled against MySQL 4.x
            $status = (bool) mysqli_query('SET NAMES ' . $this->quote($charset), $this->_connection);
        }
        else
        {
            // PHP is compiled against MySQL 5.x
            $status = mysqli_set_charset($charset, $this->_connection);
        }

        if($status === FALSE)
        {
            throw new Database_Exception(':error', array(':error' => mysqli_error($this->_connection)), mysqli_errno($this->_connection));
        }
    }

    public function query($type, $sql, $as_object)
    {
        // Make sure the database is connected
        $this->_connection or $this->connect();

        if(! empty($this->_config['profiling']))
        {
            // Benchmark this query for the current instance
            $benchmark = Profiler::start("Database ({$this->_instance})", $sql);
        }

        if(! empty($this->_config['connection']['persistent']) and $this->_config['connection']['database'] !== Database_Driver_Mysql::$_current_databases[$this->_connection_id])
        {
            // Select database on persistent connections
            $this->_select_db($this->_config['connection']['database']);
        }

        // Execute the query
        if(($result = mysqli_query($sql, $this->_connection)) === FALSE)
        {
            if(isset($benchmark))
            {
                // This benchmark is worthless
                Profiler::delete($benchmark);
            }
            throw new Database_Exception(':error [ :query ]', array(':error' => mysqli_error($this->_connection), ':query' => $sql), mysqli_errno($this->_connection));
        }

        if(isset($benchmark))
        {
            Profiler::stop($benchmark);
        }

        // Set the last query
        $this->last_query = $sql;

        if($type === Database::SELECT)
        {
            // Return an iterator of results
            return new Database_Driver_mysqli_Result($result, $sql, $as_object);
        }
        elseif($type === Database::INSERT)
        {
            // Return a list of insert id and rows created
            return array(mysqli_insert_id($this->_connection), mysqli_affected_rows($this->_connection));
        }
        else
        {
            // Return the number of rows affected
            return mysqli_affected_rows($this->_connection);
        }
    }

    public function datatype($type)
    {
        static $types = array
        (
            'blob'                      => array('type' => 'string', 'binary' => TRUE, 'character_maximum_length' => '65535'),
            'bool'                      => array('type' => 'bool'),
            'bigint unsigned'           => array('type' => 'int', 'min' => '0', 'max' => '18446744073709551615'),
            'datetime'                  => array('type' => 'string'),
            'decimal unsigned'          => array('type' => 'float', 'exact' => TRUE, 'min' => '0'),
            'double'                    => array('type' => 'float'),
            'double precision unsigned' => array('type' => 'float', 'min' => '0'),
            'double unsigned'           => array('type' => 'float', 'min' => '0'),
            'enum'                      => array('type' => 'string'),
            'fixed'                     => array('type' => 'float', 'exact' => TRUE),
            'fixed unsigned'            => array('type' => 'float', 'exact' => TRUE, 'min' => '0'),
            'float unsigned'            => array('type' => 'float', 'min' => '0'),
            'int unsigned'              => array('type' => 'int', 'min' => '0', 'max' => '4294967295'),
            'integer unsigned'          => array('type' => 'int', 'min' => '0', 'max' => '4294967295'),
            'longblob'                  => array('type' => 'string', 'binary' => TRUE, 'character_maximum_length' => '4294967295'),
            'longtext'                  => array('type' => 'string', 'character_maximum_length' => '4294967295'),
            'mediumblob'                => array('type' => 'string', 'binary' => TRUE, 'character_maximum_length' => '16777215'),
            'mediumint'                 => array('type' => 'int', 'min' => '-8388608', 'max' => '8388607'),
            'mediumint unsigned'        => array('type' => 'int', 'min' => '0', 'max' => '16777215'),
            'mediumtext'                => array('type' => 'string', 'character_maximum_length' => '16777215'),
            'national varchar'          => array('type' => 'string'),
            'numeric unsigned'          => array('type' => 'float', 'exact' => TRUE, 'min' => '0'),
            'nvarchar'                  => array('type' => 'string'),
            'point'                     => array('type' => 'string', 'binary' => TRUE),
            'real unsigned'             => array('type' => 'float', 'min' => '0'),
            'set'                       => array('type' => 'string'),
            'smallint unsigned'         => array('type' => 'int', 'min' => '0', 'max' => '65535'),
            'text'                      => array('type' => 'string', 'character_maximum_length' => '65535'),
            'tinyblob'                  => array('type' => 'string', 'binary' => TRUE, 'character_maximum_length' => '255'),
            'tinyint'                   => array('type' => 'int', 'min' => '-128', 'max' => '127'),
            'tinyint unsigned'          => array('type' => 'int', 'min' => '0', 'max' => '255'),
            'tinytext'                  => array('type' => 'string', 'character_maximum_length' => '255'),
            'year'                      => array('type' => 'string'),
        );

        $type = str_replace(' zerofill', '', $type);

        if (isset($types[$type])) return $types[$type];

        return parent::datatype($type);
    }

    public function list_tables($like = NULL)
    {
        if(is_string($like))
        {
            // Search for table names
            $result = $this->query(Database::SELECT, 'SHOW TABLES LIKE ' . $this->quote($like), FALSE);
        }
        else
        {
            // Find all table names
            $result = $this->query(Database::SELECT, 'SHOW TABLES', FALSE);
        }

        $tables = array();

        foreach ($result as $row)
        {
            $tables[] = reset($row);
        }

        return $tables;
    }

    public function list_columns($table, $like = NULL)
    {
        // Quote the table name
        $table = $this->quote_table($table);
        if(is_string($like))
        {
            // Search for column names
            $result = $this->query(Database::SELECT, 'SHOW FULL COLUMNS FROM ' . $table . ' LIKE ' . $this->quote($like), FALSE);
        }
        else
        {
            // Find all column names
            $result = $this->query(Database::SELECT, 'SHOW FULL COLUMNS FROM ' . $table, FALSE);
        }
        $count = 0;
        $columns = array();
        foreach ($result as $row)
        {
            list ($type, $length) = $this->_parse_type($row['Type']);
            $column = $this->datatype($type);
            $column['column_name'] = $row['Field'];
            $column['column_default'] = $row['Default'];
            $column['data_type'] = $type;
            $column['is_nullable'] = ($row['Null'] == 'YES');
            $column['ordinal_position'] = ++$count;
            switch ($column['type'])
            {
                case 'float' :
                    if(isset($length))
                    {
                        list ($column['numeric_precision'], $column['numeric_scale']) = explode(',', $length);
                    }
                    break;
                case 'int' :
                    if(isset($length))
                    {
                        // MySQL attribute
                        $column['display'] = $length;
                    }
                    break;
                case 'string' :
                    switch ($column['data_type'])
                    {
                        case 'binary' :
                        case 'varbinary' :
                            $column['character_maximum_length'] = $length;
                            break;
                        case 'char' :
                        case 'varchar' :
                            $column['character_maximum_length'] = $length;
                        case 'text' :
                        case 'tinytext' :
                        case 'mediumtext' :
                        case 'longtext' :
                            $column['collation_name'] = $row['Collation'];
                            break;
                        case 'enum' :
                        case 'set' :
                            $column['collation_name'] = $row['Collation'];
                            $column['options'] = explode('\',\'', substr($length, 1, - 1));
                            break;
                    }
                    break;
            }
            // MySQL attributes
            $column['comment'] = $row['Comment'];
            $column['extra'] = $row['Extra'];
            $column['key'] = $row['Key'];
            $column['privileges'] = $row['Privileges'];
            $columns[$row['Field']] = $column;
        }
        return $columns;
    }

    public function escape($value)
    {
        // Make sure the database is connected
        $this->_connection or $this->connect();
        if(($value = mysqli_real_escape_string((string) $value, $this->_connection)) === FALSE)
        {
            throw new Database_Exception(':error', array(':error' => mysqli_errno($this->_connection)), mysqli_error($this->_connection));
        }
        // SQL standard is to use single-quotes for all values
        return "'$value'";
    }
}
